package com.neusoft.bizcore.webauth.secret;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.DigestUtils;

import com.neusoft.bizcore.auth.common.bean.UserBean;
import com.neusoft.bizcore.auth.common.jwt.JwtAuthProvider;
import com.neusoft.bizcore.web.dto.result.ResultDTO;
import com.neusoft.bizcore.web.utils.JsonUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
@EnableWebSecurity
@ComponentScan
public class BizcoreSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private BizcoreWebAuthProperties properties;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private WebAuthMicroService webAuthMicroService;
    @Autowired
    private AuditOplogService auditOplogService;

    @Autowired
    @Override
    public void configure(final AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
        auth.userDetailsService(this.userDetailsService).passwordEncoder(this.md5PasswordEncoder());
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()

                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .formLogin()
                .disable()
                //---自定义JSON认证逻辑 开始
                .apply(new JsonLoginConfigurer<>())
                .usernameParameter(this.properties.getUsernameParameter())
                .passwordParameter(this.properties.getPasswordParameter())
                .loginSuccessHandler(this.authenticationSuccessHandler(this.jwtAuthProvider()))
                .loginFailureHandler(this.authenticationFailureHandler())
                .loginProcessesUrl(this.properties.getLoginProcessingUrl())
                //---自定义JSON认证逻辑 结束
                .and()
                .logout()
                .logoutUrl(this.properties.getLogoutProcessingUrl())
                .logoutSuccessHandler(this.logoutSuccessHandler())
                .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**")
                .permitAll()
                .antMatchers(this.properties.getPermitAll().toArray(new String[0]))
                .permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(this.jwtAuthTokenFilter(), LogoutFilter.class);
        http.headers().cacheControl();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(this.properties.getIgnores().toArray(new String[0]));
        super.configure(web);
    }

    @Bean
    @ConditionalOnMissingBean(UserDetailsService.class)
    public UserDetailsService remoteUserDetailsService() {
        return username -> {
            final ResultDTO<UserBean> result =
                    BizcoreSecurityConfiguration.this.webAuthMicroService.loadUserByUsername(username);

            final UserBean userBean = result.getData();
            final Collection<? extends GrantedAuthority> authorities = userBean.getRoles().stream()
                    .map(role -> new SimpleGrantedAuthority(role.getRole())).collect(Collectors.toSet());

            final User user = new User(userBean.getUsername(), userBean.getPassword(), authorities);
            return user;
        };
    }

    @Bean
    public JwtAuthProvider jwtAuthProvider() {
        return new JwtAuthProvider(this.properties.getJwtExpiration(), this.properties.getJwtSigningKey());
    }

    @Bean
    public JwtAuthTokenFilter jwtAuthTokenFilter() {
        return new JwtAuthTokenFilter(this.jwtAuthProvider());
    }

    @Bean
    public PasswordEncoder md5PasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(final CharSequence charSequence) {
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }

            @Override
            public boolean matches(final CharSequence charSequence, final String s) {
                final String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                final boolean res = s.equals(encode);
                return res;
            }
        };
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler(final JwtAuthProvider jwtAuthProvider) {
        final AuthenticationSuccessHandler handler = (request, response, authentication) -> {
            BizcoreSecurityConfiguration.log.info("登录成功");

            this.auditOplogService.create(authentication.getName(), request.getHeader("x-forwarded-for") == null
                    ? request.getRemoteAddr() : request.getHeader("x-forwarded-for"), "3D可视化", "Login", "登录成功");

            final List<String> roles = ((User) authentication.getPrincipal()).getAuthorities().stream()
                    .map(it -> it.getAuthority()).collect(Collectors.toList());
            final String token =
                    jwtAuthProvider.generateToken(((User) authentication.getPrincipal()).getUsername(), roles);
            response.setContentType("application/json;charset=UTF-8");
            final ResultDTO<String> result = ResultDTO.success(token);
            response.getWriter().write(JsonUtils.pojoToJson(result));
        };

        return handler;
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        final AuthenticationFailureHandler handler =
                (request, response, exception) -> response.setStatus(HttpStatus.UNAUTHORIZED.value());
        return handler;
    }

    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        final LogoutSuccessHandler handler = (request, response, authentication) -> {
            if (null != authentication) {
                this.auditOplogService.create(authentication.getName(), request.getHeader("x-forwarded-for") == null
                        ? request.getRemoteAddr() : request.getHeader("x-forwarded-for"), "3D可视化", "Logout",
                        "登出成功");
            }
            BizcoreSecurityConfiguration.log.info("退出成功");
            response.setContentType("application/json;charset=UTF-8");
            final ResultDTO<Void> result = ResultDTO.success();
            response.getWriter().write(JsonUtils.pojoToJson(result));
        };
        return handler;

    }

}
